#include <rtthread.h>
#include <rthw.h>
#include <rtdevice.h>
#include <stdbool.h>

#define read32(addr)           (*(volatile unsigned int *)(addr))
#define read8(addr) 			( *(volatile unsigned char *)(addr))
#define write32(addr, value)    (*(volatile unsigned int *)(addr) = (value))
#define write8(addr, value)    (*(volatile unsigned char *)(addr) = (value))

#define SPI0_BASE_ADDR 0x04025000

enum {
	SPI_GCR	= 0x04,
	SPI_TCR	= 0x08,
	SPI_IER	= 0x10,
	SPI_ISR	= 0x14,
	SPI_FCR	= 0x18,
	SPI_FSR	= 0x1c,
	SPI_WCR	= 0x20,
	SPI_CCR	= 0x24,
	SPI_MBC	= 0x30,
	SPI_MTC	= 0x34,
	SPI_BCC	= 0x38,
	SPI_TXD	= 0x200,
	SPI_RXD	= 0x300,
};

void xboot_spi_init(void)
{
	uint32_t addr;
	uint32_t val;

	/* Config GPIOC2, GPIOC3, GPIOC4 and GPIOC5 */
	addr = 0x02000060 + 0x00;
	val = read32(addr);
	val &= ~(0xf << ((2 & 0x7) << 2));
	val |= ((0x2 & 0xf) << ((2 & 0x7) << 2));
	write32(addr, val);

	val = read32(addr);
	val &= ~(0xf << ((3 & 0x7) << 2));
	val |= ((0x2 & 0xf) << ((3 & 0x7) << 2));
	write32(addr, val);

	val = read32(addr);
	val &= ~(0xf << ((4 & 0x7) << 2));
	val |= ((0x2 & 0xf) << ((4 & 0x7) << 2));
	write32(addr, val);

	val = read32(addr);
	val &= ~(0xf << ((5 & 0x7) << 2));
	val |= ((0x2 & 0xf) << ((5 & 0x7) << 2));
	write32(addr, val);

	/* Deassert spi0 reset */
	addr = 0x0200196c;
	val = read32(addr);
	val |= (1 << 16);
	write32(addr, val);

	/* Open the spi0 gate */
	addr = 0x02001940;
	val = read32(addr);
	val |= (1 << 31);
	write32(addr, val);

	/* Open the spi0 bus gate */
	addr = 0x0200196c;
	val = read32(addr);
	val |= (1 << 0);
	write32(addr, val);

	/* Select pll-periph0 for spi0 clk */
	addr = 0x02001940;
	val = read32(addr);
	val &= ~(0x3 << 24);
	val |= 0x1 << 24;
	write32(addr, val);

	/* Set clock pre divide ratio, divided by 1 */
	addr = 0x02001940;
	val = read32(addr);
	val &= ~(0x3 << 8);
	val |= 0x0 << 8;
	write32(addr, val);

	/* Set clock divide ratio, divided by 6 */
	addr = 0x02001940;
	val = read32(addr);
	val &= ~(0xf << 0);
	val |= (6 - 1) << 0;
	write32(addr, val);

	/* Set spi clock rate control register, divided by 2 */
	addr = 0x04025000;
	write32(addr + SPI_CCR, 0x1000);

	/* Enable spi0 and do a soft reset */
	addr = 0x04025000;
	val = read32(addr + SPI_GCR);
	val |= (1 << 31) | (1 << 7) | (1 << 1) | (1 << 0);
	write32(addr + SPI_GCR, val);
	while(read32(addr + SPI_GCR) & (1 << 31));

	val = read32(addr + SPI_TCR);
	val &= ~(0x3 << 0);
	val |= (1 << 6) | (1 << 2);
	write32(addr + SPI_TCR, val);

	val = read32(addr + SPI_FCR);
	val |= (1 << 31) | (1 << 15);
	write32(addr + SPI_FCR, val);
}

static void sys_spi_write_txbuf(uint8_t * buf, int len)
{
	uint32_t addr = SPI0_BASE_ADDR;
	int i;

	write32(addr + SPI_MTC, len & 0xffffff);
	write32(addr + SPI_BCC, len & 0xffffff);
	if(buf)
	{
		for(i = 0; i < len; i++)
			write8(addr + SPI_TXD, *buf++);
	}
	else
	{
		for(i = 0; i < len; i++)
			write8(addr + SPI_TXD, 0xff);
	}
}

static void sys_spi_select(void)
{
	uint32_t addr = SPI0_BASE_ADDR;
	uint32_t val;

	val = read32(addr + SPI_TCR);
	val &= ~((0x3 << 4) | (0x1 << 7));
	val |= ((0 & 0x3) << 4) | (0x0 << 7);
	write32(addr + SPI_TCR, val);
}

static void sys_spi_deselect(void)
{
	uint32_t addr = SPI0_BASE_ADDR;
	uint32_t val;

	val = read32(addr + SPI_TCR);
	val &= ~((0x3 << 4) | (0x1 << 7));
	val |= ((0 & 0x3) << 4) | (0x1 << 7);
	write32(addr + SPI_TCR, val);
}

static rt_uint32_t xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
	uint32_t addr = SPI0_BASE_ADDR;
	int count = message->length;
	uint8_t * rx = message->recv_buf;
	uint8_t * tx = message->send_buf;
	uint8_t val;
	int n, i;

	uint32_t rx_len = message->recv_buf == RT_NULL ? 0 : message->length;
    uint32_t tx_len = message->send_buf == RT_NULL ? 0 : message->length;

	// rt_kprintf("xfer %d %02x\n", count, tx[0]);
	// rt_kprintf("%d %d %02x\n", rx_len, tx_len, tx[0]);

	if (message->cs_take)
    {
        sys_spi_select();
    }

	while(count > 0)
	{
		n = (count <= 64) ? count : 64;
		write32(addr + SPI_MBC, n);
		sys_spi_write_txbuf(tx, n);
		write32(addr + SPI_TCR, read32(addr + SPI_TCR) | (1 << 31));
		while(read32(addr + SPI_TCR) & (1 << 31));

		while((read32(addr + SPI_FSR) & 0xff) < n);
		for(i = 0; i < n; i++)
		{
			val = read8(addr + SPI_RXD);
			if(rx)
				*rx++ = val;
		}

		if(tx)
			tx += n;
		count -= n;
	}
	// rt_kprintf("hal_spi_xfer %d %d\n", message->cs_release, message->cs_take);

	if (message->cs_release)
    {
        sys_spi_deselect();
    }
	return message->length;
}

static rt_err_t configure(struct rt_spi_device *device,
                          struct rt_spi_configuration *configuration)
{
	return RT_EOK;
}

static struct rt_spi_ops spi_ops =
{
    configure,
    xfer
};


static struct rt_spi_bus spi_bus0;


rt_err_t xboot_spi_bus_register(const char *spi_bus_name)
{
	
    rt_spi_bus_register(&spi_bus0, spi_bus_name, &spi_ops);
    return RT_EOK;
}

int rt_boot_spi_init(void)
{
	static struct rt_spi_device spi_device;

    /* register spi bus */
    {
        rt_err_t result;

        xboot_spi_init();

        result = xboot_spi_bus_register("spi0");
        if (result != RT_EOK)
        {
            return result;
        }
    }

    {
        rt_err_t result;

        result = rt_spi_bus_attach_device(&spi_device, "spi00", "spi0", RT_NULL);
        if (result != RT_EOK)
        {
            return result;
        }
    }


    return RT_EOK;
}
INIT_BOARD_EXPORT(rt_boot_spi_init);

